// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//
#pragma once

#include <cmath>
#include <pollux/expression/complex_view_types.h>
#include <pollux/functions/macros.h>

namespace kumo::pollux::functions {
    namespace details {
        /**
         * This class implements two functions:
         *
         * greatest(value1, value2, ..., valueN) → [same as input]
         * Returns the largest of the provided values.
         *
         * least(value1, value2, ..., valueN) → [same as input]
         * Returns the smallest of the provided values.
         *
         * For DOUBLE and REAL type, NaN is considered as the biggest according to
         * https://github.com/prestodb/presto/issues/22391
         **/
        template<typename TExec, typename T, bool isLeast>
        struct ExtremeValueFunction {
            POLLUX_DEFINE_FUNCTION_TYPES(TExec);

            MELON_ALWAYS_INLINE void call(
                out_type<T> &result,
                const arg_type<T> &firstElement,
                const arg_type<Variadic<T> > &remainingElement) {
                auto currentValue = extractValue(firstElement);

                for (auto element: remainingElement) {
                    auto candidateValue = extractValue(element.value());

                    if constexpr (isLeast) {
                        if (smallerThan(candidateValue, currentValue)) {
                            currentValue = candidateValue;
                        }
                    } else {
                        if (greaterThan(candidateValue, currentValue)) {
                            currentValue = candidateValue;
                        }
                    }
                }

                result = currentValue;
            }

        private:
            template<typename U>
            auto extractValue(const U &wrapper) const {
                return wrapper;
            }

            template<typename U>
            U extractValue(
                const exec::CustomTypeWithCustomComparisonView<U> &wrapper) const {
                return *wrapper;
            }

            template<typename K>
            bool greaterThan(const K &lhs, const K &rhs) const {
                if constexpr (std::is_same_v<K, double> || std::is_same_v<K, float>) {
                    if (std::isnan(lhs)) {
                        return true;
                    }

                    if (std::isnan(rhs)) {
                        return false;
                    }
                }

                return lhs > rhs;
            }

            template<typename K>
            bool smallerThan(const K &lhs, const K &rhs) const {
                if constexpr (std::is_same_v<K, double> || std::is_same_v<K, float>) {
                    if (std::isnan(lhs)) {
                        return false;
                    }

                    if (std::isnan(rhs)) {
                        return true;
                    }
                }

                return lhs < rhs;
            }
        };
    } // namespace details

    template<typename TExec, typename T>
    using LeastFunction = details::ExtremeValueFunction<TExec, T, true>;

    template<typename TExec, typename T>
    using GreatestFunction = details::ExtremeValueFunction<TExec, T, false>;
} // namespace kumo::pollux::functions
